/* Copyright (C) 2003, Charles Wang.
 * Author:  Charles Wang <charles@linux.net.cn>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANT; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public LIcense for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "loop.h"
#include "mxml-1.0/mxml.h"
#include "mount_guess_fstype.h"
#include "proc-parser.h"
#include "testshow.h"

#ifdef NOINIT
#define  MBPREFIX    "/tmp/mbroot"
#else
#define  MBPREFIX
#endif

/*--- Public varibles to help to generate output ---*/
typedef void (*msgf)(int, const char *, ...);
static   msgf  mfunc;
static   int   mdev;
static   int   mblogfd;

static void mblog_start(void);
static void mblog_msg(int, const char *, ...);
static void mblog_end(void);

#define  MBLOG(x)  { mfunc x; mblog_msg x; }

#ifdef TEST
#define  TESTMSG(x)    MBLOG(x)
#else
#define  TESTMSG(x)    ((void)0)
#endif

/*--- The code to make busybox-0.60.5/init.c satification ---*/

char *applet_name;
void show_usage(void) {}

typedef int (*applet_main)(int, char *argv[]);
typedef struct {
    const char  *appname;
    applet_main  appmain;
}  appinfo;

/*  Refer the README in this directory before add/remove of the included applets. */
extern int cpio_main(int argc, char *argv[]);
extern int gunzip_main(int argc, char *argv[]);
extern int init_main(int argc, char *argv[]);
extern int insmod_main(int argc, char *argv[]);
extern int modprobe_main(int argc, char *argv[]);
extern int rmmod_main(int argc, char *argv[]);
extern int wget_main(int argc, char *argv[]);

appinfo applet_list[] = {
    { "cpio",      cpio_main },
    { "gunzip",    gunzip_main },
    { "init",      init_main },
    { "insmod",    insmod_main },
    { "modprobe",  modprobe_main },
    { "rmmod",     rmmod_main },
    { "wget",      wget_main },
};


int MagicBooter_main(msgf msgfunc, int msgdevice,
		     int argc, char *argv[]);

#ifdef NOINIT
void fdprintf(int fd, const char *fmt, ...) {
    char  buf[256];
    va_list  ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    write(fd, buf, strlen(buf));
    va_end(ap);
}
int main(int argc, char *argv[]) {
    return  MagicBooter_main(fdprintf, 1, argc, argv);
}
#else
int main(int argc, char *argv[]) {
    int       found;
    int       ret;
    char     *arg0;
    appinfo  *ai;

    found = 0;
    arg0 = strdup(argv[0]);  applet_name = basename(arg0);
    for (ai = applet_list;
	 ai - applet_list < sizeof(applet_list) / sizeof(applet_list[0]);
	 ++ai)
	if (!strcmp(ai->appname, applet_name)) {
	    ret = ai->appmain(argc, argv);
	    found = 1;
	    break;
	}
    if (!found)  fprintf(stderr, "Not supported applet %s.\n", applet_name); 
    free(arg0);
    return  ret;
}
#endif


/*--- For configuration ---*/
/* Collect all probe type here. */
#define  MBPROBE_IDE_CDROM       "ide-cdrom"
#define  MBPROBE_IDE_HARDDISK    "ide-harddisk"
#define  MBPROBE_USB_STORAGE     "usb-storage"
#define  MBPROBE_SCSI_CDROM      "scsi-cdrom"
#define  MBPROBE_SCSI_HARDDISK   "scsi-harddisk"
#define  MBPROBE_ALL             "all"

static const char *default_mbconfig =
"<?xml version=\"1.0\"?>\n"
"<MagicBooter>\n"
"    <probes>\n"
"        <probe pathspec=\"/\">usb-storage</probe>\n"
"        <probe pathspec=\"/%s\">ide-cdrom</probe>\n"
"        <probe>ide-harddisk</probe>\n"
"        <probe pathspec=\"/%s\">scsi-cdrom</probe>\n"
"        <probe>scsi-harddisk</probe>\n"
"        <probe>all</probe>\n"
"    </probes>\n"
"    <pathspec>\n"
"        <path>/boot</path>\n"
"        <path>/%s</path>\n"
"        <path>/usr/share/MagicInstaller</path>\n"
"        <path>/</path>\n"
"        <path>/tmp</path>\n"
"    </pathspec>\n"
"</MagicBooter>\n";

static mxml_node_t * xmldoc;
static mxml_node_t * xmlroot;
static mxml_node_t * probes;
static mxml_node_t * default_pathspec;

static int  parse_config_file(const char *filename);

/*--- Public variables for mi_initrd. ---*/
static const char *mi_initrd_device = "/dev/ram1";
static const char *mi_initrd_file = "miinitrd";
/* mi_initrd_root: The directory to mount mi_initrd_file. */
#define mi_initrd_root  "/tmp/root.mnt"

static const char *mi_initrd_cramfs_device = "/dev/loop6";
static const char *mi_initrd_cramfs_file = "/tmp/root.mnt/miinitrd.cramfs";
/* mi_initrd_cramfs_root: The directory to mount mi_initrd_cramfs_file. */
#define mi_initrd_cramfs_root  "/tmp/root.cramfs.mnt"

/* mi_device_root: The directory to mount the real harddisk/cdrom device. */
static const char *mi_device_root = MBPREFIX "/tmp/device.mnt";
/* mi_initrd_fullpath: The path to the accessable mi_initrd_file.
 *  It will be subdirectory of mi_device_root or mi_isoloop_root. */
static char mi_initrd_fullpath[256];

#define LOOP_DEVFILE  "/dev/loop7"
/* mi_isoloop_root: The directory to mount the iso as loop device. */
static const char *mi_isoloop_root = MBPREFIX "/tmp/isoloop.mnt";
/* mi_isofile_fullpath: The path to the bootcd iso file.
 *  mi_isofile_fullpath[0] == 0 means it is not used. */
static char mi_isofile_fullpath[256];

/* The device filename which contain the miinitrd or the .iso file. */
static char *respos_device = NULL;
/* The type of the filesystem in the respos_device. */
static char *respos_fstype = NULL;
/* The path to the miinitrd or .iso file in the device. */
static char *respos_path   = NULL;
/* Whether the miinitrd is relied in .iso file. */
static int   respos_in_iso;

int lomount(const char *device, const char *file, const char *mntdir, const char *fstype);
int loumount(const char *device, const char *mntdir);

/*--- Public variables for hardware information. ---*/
cpuinfo_struct    cpuinfo;
meminfo_struct    meminfo;
filesystem_array  filesystems;
ide_array         ides;
scsi_array        scsis;
partition_array   partitions;

static int  parse_proc_files(void);
static void touch_all_ide_cdroms(void);
static void touch_all_scsi_cdroms(void);


static int  search_full_image(void);


/* MagicBooter_main has to be sleep some seconds to wait the start up of
 * some special device. */
#define  USB_STORAGE_SLEEPS   1

static size_t lbsizes[] = {
    4*1024*1024,
    1024*1024,
    256*1024
};
int MagicBooter_main(msgf msgfunc, int msgdevice,
		     int argc, char *argv[]) {
    int       i;
    unsigned  lbsize;
    char     *loadbuffer;
    int       rfd, wfd;
    ssize_t   rl, wl, wlsum;
    unsigned long bytes_loaded;
#ifdef  TEST
    char     *curlb;
    ssize_t   rl0, rl1;
    unsigned long addsum;
#endif
    FILE     *respy_file;

    mfunc = msgfunc;  mdev = msgdevice;

#ifndef  TEST
    MBLOG((mdev, "*** MagicBooter Started ***\n"));
#else
#ifdef  NOINIT
    TESTMSG((mdev, "NOINIT TEST VERSION OF MAGICBOOTER.\n"));
#else
    TESTMSG((mdev, "TEST VERSION OF MAGICBOOTER.\n"));
#endif
#endif

#ifndef  NOINIT
    /* Remount / in read-write mode. */
    if (mount("rootfs", "/", "rootfs", MS_REMOUNT, NULL) != 0) {
	MBLOG((mdev, "Can't remount rootfs.\n"));
	return 1;
    }

    mblog_start();

    /* Mount /proc. */
    if (mount(NULL, "/proc", "proc", MS_MGC_VAL, NULL) != 0) {
	MBLOG((mdev, "Can't mount proc.\n"));
	return 1;
    }
#endif

    /* parse config file if it is present. */
    MBLOG((mdev, "Load MagicBooter configuration.\n"));
    if (!parse_config_file(MBPREFIX "/mb-config.xml")) {
	MBLOG((mdev, "Not enough memory or can't find 'probes' nodes in configuration.\n"));
	return 1;
    }

    sleep(USB_STORAGE_SLEEPS);

    if (parse_proc_files())  return 1;
    SHOWTEXT(mfunc, mdev, "/proc/filesystems");

    if (!search_full_image()) {
	MBLOG((mdev, "Couldn't get full image from all possible place specified by configuration.\n"));
	return 1;
    }

    MBLOG((mdev, "\n"));

    /* Load the full image into memory. */
    for (i = 0; i < sizeof(lbsizes)/sizeof(lbsizes[0]); ++i) {
	loadbuffer = malloc(lbsize = lbsizes[i]);
	if (loadbuffer)  break;
    }
    if (loadbuffer == NULL) {
	MBLOG((mdev, "Not enough memory to load the full image.\n"));
	return 1;
    }

    MBLOG((mdev, "Loading (%s -> %s)\n", mi_initrd_fullpath, mi_initrd_device));

    bytes_loaded = 0;
    rfd = open(mi_initrd_fullpath, O_RDONLY);
    wfd = open(mi_initrd_device, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0666);
    if (rfd >= 0 && wfd >= 0) {
	wlsum = 0;
	while ((rl = read(rfd, loadbuffer, lbsize)) > 0) {
	    wl = write(wfd, loadbuffer, rl);
	    if (wl != rl) {
		MBLOG((mdev, "\nWrite %s error (Read %d bytes but write %d bytes).\n",
		      mi_initrd_device, rl, wl));
		MBLOG((mdev, "Please ensure ramdisk_size=%d\n", EXT2FSSIZE * 1024));
		return 1;
	    }
	    wlsum += wl;
	    if (wlsum >= 1024*1024) {
		for (i = 0; i < wlsum / (1024*1024); ++i)
		    MBLOG((mdev, "."));
		wlsum = 0;
	    }
	    bytes_loaded += wl;
	}
	fsync(wfd);
    }
    if (rfd >= 0)  close(rfd);
    if (wfd >= 0)  close(wfd);
    if (rfd < 0 || wfd < 0) {
	MBLOG((mdev, "Open failed: Can not load full image into ramdisk.\n"));
	return 1;
    }

    MBLOG((mdev, " OK (%lu bytes loaded)!\n", bytes_loaded));

#ifdef  TEST
    addsum = 0;
    MBLOG((mdev, "Check the image in memory: "));
    rfd = open(mi_initrd_fullpath, O_RDONLY);
    wfd = open(mi_initrd_device, O_RDONLY);
    if (rfd < 0 || wfd < 0) {
	MBLOG((mdev, "Open failed.\n"));
	return 1;
    } else {
	bytes_loaded = 0;
	while ((rl0 = read(rfd, loadbuffer, lbsize / 2)) > 0) {
	    rl1 = read(wfd, loadbuffer + lbsize / 2, rl0);
	    if (rl0 != rl1) {
		MBLOG((mdev, "Compare failed on %lu [rl0 = %d, rl1 = %d].\n",
		      bytes_loaded, rl0, rl1));
		return 1;
	    }
	    if (memcmp(loadbuffer, loadbuffer + lbsize / 2, rl0)) {
		MBLOG((mdev, "Compare failed [%lu-%lu].\n",
		      bytes_loaded, bytes_loaded + rl0));
		return 1;
	    }
	    for (curlb = loadbuffer; curlb < loadbuffer + rl0; ++curlb)
		addsum += *curlb;
	    bytes_loaded += rl0;
	}
	close(rfd);  close(wfd);
    }
    MBLOG((mdev, "OK (Addsum = %lx)!\n", addsum));
#endif

    if (mi_isofile_fullpath[0] && loumount(LOOP_DEVFILE, mi_isoloop_root)) {
	MBLOG((mdev, "Can't umount %s\n", mi_isoloop_root));
	return 1;
    }

    if (umount(mi_device_root)) {
	MBLOG((mdev, "Can't umount %s\n", mi_device_root));
	return 1;
    }

    MBLOG((mdev, "Mount filesystem on %s(%s): ", mi_initrd_device, "ext2"));

    if(mount(mi_initrd_device, MBPREFIX mi_initrd_root, "ext2", MS_RDONLY, NULL)) {
	SHOWBIN(mfunc, mdev, mi_initrd_device, 32);
	MBLOG((mdev, "Mount failed(errno -> %s(%d)).\n", strerror(errno), errno));
	return 1;
    }

    MBLOG((mdev, "OK!\n"));

    MBLOG((mdev, "Mount filesystem on %s(%s): ", mi_initrd_cramfs_device, "cramfs"));

    if (lomount(mi_initrd_cramfs_device, MBPREFIX mi_initrd_cramfs_file,
		MBPREFIX mi_initrd_cramfs_root, "cramfs")) {
	SHOWBIN(mfunc, mdev, mi_initrd_cramfs_device, 32);
	MBLOG((mdev, "LoMount failed(errno -> %s(%d)).\n", strerror(errno), errno));
	return 1;
    }

    MBLOG((mdev, "OK!\n"));

    /* Create the result in a python script. */
    MBLOG((mdev, "Create /mbresult.py: "));
    respy_file = fopen("/mbresult.py", "a");
    if (respy_file == NULL) {
	MBLOG((mdev, "Failed (errno = %d).\n", errno));
    } else {
	fprintf(respy_file, "#!/usr/bin/python\n\n");
	fprintf(respy_file, "miinitrd_pos = [\"%s\", \"%s\", \"%s\", \"%s\"]\n",
		respos_device ? respos_device : "",
		respos_fstype ? respos_fstype : "",
		respos_path ? respos_path : "",
		respos_in_iso ? BOOTISO_FN : "");
	fclose(respy_file);
	MBLOG((mdev, "OK\n"));
    }
    if (respos_device)  { free(respos_device);  respos_device = NULL; }
    if (respos_fstype)  { free(respos_fstype);  respos_fstype = NULL; }
    if (respos_path)    { free(respos_path);    respos_path   = NULL; }

#ifndef NOINIT
    /* Dive into it. */
    MBLOG((mdev, "Pivot root into the full image: "));
    if (pivot_root(mi_initrd_cramfs_root, mi_initrd_cramfs_root "/mnt/MagicBooter")) {
	MBLOG((mdev, "Failed(errno = %d).\n", errno));
	return 1;
    }
#endif

    MBLOG((mdev, "OK!\n"));

    mblog_end();

    /* All successful, go to run the full image. */
    return 0;
}

static int  parse_config_file(const char *filename) {
    FILE *fp;  mxml_node_t  *curnode;
    char defconf[1024];

    /* Read the config in. */
    if ((fp = fopen(filename, "r")) == NULL) {
	snprintf(defconf, sizeof(defconf),
		 default_mbconfig, MIIMAGES_CDDIR, MIIMAGES_CDDIR, DISTNAME);
	if ((fp = fmemopen(defconf, strlen(defconf), "r")) == NULL) {
	    return  0;
	}
    }
    xmldoc = mxmlLoadFile(MXML_NO_CALLBACK, fp, MXML_NO_PARENT);
    if (xmldoc == NULL)  return 0;
    SHOWXML(mfunc, mdev, xmldoc);
    fclose(fp);

    /* Search probes and default_pathspec. */
    probes = NULL;  default_pathspec = NULL;
    xmlroot = mxmlFindElement(xmldoc, xmldoc, "MagicBooter",
			      NULL, NULL, MXML_DESCEND_FIRST);
    if (xmlroot == NULL)  return 0;
    for (curnode = xmlroot->child; curnode != NULL; curnode = curnode->next) {
	if (curnode->type == MXML_ELEMENT) {
	    if (!probes
		&& !strcmp(curnode->value.element.name, "probes")) {
		probes = curnode;
		if (default_pathspec)  break;
	    }
	    if (!default_pathspec
		&& !strcmp(curnode->value.element.name, "pathspec")
		&& mxmlElementGetAttr(curnode, "name") == NULL) {
		default_pathspec = curnode;
		if (probes)  break;
	    }
	}
    }
    return  probes != NULL;
}

static int parse_proc_files(void) {
    /* Parse system information in /proc. */
    if (parse_cpuinfo(&cpuinfo)) {
	MBLOG((mdev, "Can't open/read from /proc/cpuinfo or not enough memory.\n"));
	return 1;
    }
    if (parse_meminfo(&meminfo)) {
	MBLOG((mdev, "Can't open/read from /proc/meminfo or not enough memory.\n"));
	return 1;
    }
    if (parse_filesystems(&filesystems)) {
	MBLOG((mdev, "Can't open/read from /proc/filesystems or not enough memory.\n"));
	return 1;
    }
    if (parse_ides(&ides)) {
	ides.num = 0;
	ides.next = NULL;
	MBLOG((mdev, "Not any ide device detected.\n"));
    }
    if (parse_scsis(&scsis)) {
	scsis.num = 0;
	scsis.next = NULL;
	MBLOG((mdev, "Not any scsi device detected.\n"));
    }
    if (parse_partitions(&partitions)) {
	MBLOG((mdev, "Can't open/read from /proc/partitions or not enough memory.\n"));
	return 1;
    }
    return 0;
}

static unsigned ide_rdev[] = {
    makedev( 3, 0), makedev( 3, 64),
    makedev(22, 0), makedev(22, 64),
    makedev(33, 0), makedev(33, 64),
    makedev(34, 0), makedev(34, 64),
    makedev(56, 0), makedev(56, 64),
    makedev(57, 0), makedev(57, 64),
    makedev(88, 0), makedev(88, 64),
    makedev(89, 0), makedev(89, 64),
    makedev(90, 0), makedev(90, 64),
    makedev(91, 0), makedev(91, 64)
};
static void touch_all_ide_cdroms(void) {
    int          i, fd;  char fnbuf[64];
    ide_array   *cur_ides;
    for (cur_ides = &ides; cur_ides != NULL; cur_ides = cur_ides->next)
	for (i = 0; i < cur_ides->num; ++i)
	    if (!(cur_ides->diskmask & (1 << i)) && cur_ides->capacity[i] > 0) {
		/* It is a CDROM with CD in it, touch is required. */
		snprintf(fnbuf, sizeof(fnbuf), MBPREFIX "/tmp/%s", cur_ides->devs[i]);
		mknod(fnbuf, 0660 | S_IFBLK, ide_rdev[cur_ides->devs[i][2] - 'a']);
		fd = open(fnbuf, O_RDONLY);  close(fd);
		unlink(fnbuf);
	    }
}

static unsigned scsi_rdev[] = {
    makedev( 8,   0), makedev( 8,  16),
    makedev( 8,  32), makedev( 8,  48),
    makedev( 8,  64), makedev( 8,  80),
    makedev( 8,  96), makedev( 8, 112),
    makedev( 8, 128), makedev( 8, 144),
    makedev( 8, 160), makedev( 8, 176),
    makedev( 8, 192), makedev( 8, 208),
    makedev( 8, 224), makedev( 8, 240),
    makedev(65,   0), makedev(65,  16),
    makedev(65,  32), makedev(65,  48),
    makedev(65,  64), makedev(65,  80),
    makedev(65,  96), makedev(65, 112),
    makedev(65, 128), makedev(65, 144)
};
static void touch_all_scsi_cdroms(void) {
    int          i, fd;  char fnbuf[64];
    scsi_array  *cur_scsis;
    for (cur_scsis = &scsis; cur_scsis != NULL; cur_scsis = cur_scsis->next)
	for (i = 0; i < cur_scsis->num; ++i)
	    if (cur_scsis->types[i] == MISCSI_CDROM) { /* NO TEST YET */
		/* It is a usb storage, touch is required. */
		snprintf(fnbuf, sizeof(fnbuf), MBPREFIX "/tmp/%s", cur_scsis->devs[i]);
		mknod(fnbuf, 0660 | S_IFBLK, scsi_rdev[cur_scsis->devs[i][2] - 'a']);
		fd = open(fnbuf, O_RDONLY);  close(fd);
		unlink(fnbuf);
	    }
}

#define  TESTED_BUFSIZE   1024
static int  not_part(const char *dev, const char *devices, const char *terminate);
static int  probe_ide_device(const ide_array *ides, int i, const char *pathspec);
static int  probe_scsi_device(const scsi_array *scsis, int i, const char *pathspec);
static int  probe_filesystem(const partition_array *parts, int i, const char *pathspec);
static int  probe_device(const char *device, const char *pathspec);
static int  search_full_image(void) {
    int   i;
    int   ide_cdrom_touched, scsi_cdrom_touched;
    const char *cur_probes_pathspec;
    const mxml_node_t *cur_probe;
    const char *cur_probe_text, *cur_pathspec;
    char  tested_hd[TESTED_BUFSIZE], *freehd = tested_hd;
    char  tested_sd[TESTED_BUFSIZE], *freesd = tested_sd;
    const ide_array *cur_ides;
    const scsi_array  *cur_scsis;

    ide_cdrom_touched = scsi_cdrom_touched = 0;
    cur_probes_pathspec = mxmlElementGetAttr(probes, "pathspec");

    for (cur_probe = probes->child;
	 cur_probe != NULL;
	 cur_probe = cur_probe->next) {
	if (cur_probe->type != MXML_ELEMENT)  continue;
	if (strcmp(cur_probe->value.element.name, "probe"))  continue;
	if (cur_probe->child->type != MXML_TEXT)  continue;

	cur_probe_text = cur_probe->child->value.text.string;
	cur_pathspec = mxmlElementGetAttr((mxml_node_t *)cur_probe, "pathspec");
	if (cur_pathspec == NULL)  cur_pathspec = cur_probes_pathspec;

	if (!strcmp(cur_probe_text, MBPROBE_IDE_CDROM)) {
	    if (!ide_cdrom_touched) {
		free_partitions(&partitions);
		touch_all_ide_cdroms();
		parse_partitions(&partitions);
	    }
	    for (cur_ides = &ides; cur_ides != NULL; cur_ides = cur_ides->next)
		for (i = 0; i < cur_ides->num; ++i)
		    if (!(cur_ides->diskmask & (1 << i))
			&& not_part(cur_ides->devs[i], tested_hd, freehd)) {
			if (probe_ide_device(cur_ides, i, cur_pathspec))  return 1;
			strcpy(freehd, cur_ides->devs[i] + 2);
			freehd += strlen(cur_ides->devs[i] + 2) + 1;
		    }
	} else if (!strcmp(cur_probe_text, MBPROBE_IDE_HARDDISK)) {
	    for (cur_ides = &ides; cur_ides != NULL; cur_ides = cur_ides->next)
		for (i = 0; i < cur_ides->num; ++i)
		    if (cur_ides->diskmask & (1 << i)
			&& not_part(cur_ides->devs[i], tested_hd, freehd)) {
			if (probe_ide_device(cur_ides, i, cur_pathspec))  return 1;
			strcpy(freehd, cur_ides->devs[i] + 2);
			freehd += strlen(cur_ides->devs[i] + 2) + 1;
		    }
	} else if (!strcmp(cur_probe_text, MBPROBE_USB_STORAGE)) {
	    for (cur_scsis = &scsis; cur_scsis != NULL; cur_scsis = cur_scsis->next)
		for (i = 0; i < cur_scsis->num; ++i)
		    if (cur_scsis->types[i] == MISCSI_USBSTORAGE
			&& not_part(cur_scsis->devs[i], tested_sd, freesd)) {
			if (probe_scsi_device(cur_scsis, i, cur_pathspec))  return 1;
			strcpy(freesd, cur_scsis->devs[i] + 2);
			freesd += strlen(cur_scsis->devs[i] + 2) + 1;
		    }
	} else if (!strcmp(cur_probe_text, MBPROBE_SCSI_CDROM)) {
	    if (!scsi_cdrom_touched) {
		free_partitions(&partitions);
		touch_all_scsi_cdroms();
		parse_partitions(&partitions);
	    }
	    for (cur_scsis = &scsis; cur_scsis != NULL; cur_scsis = cur_scsis->next)
		for (i = 0; i < cur_scsis->num; ++i)
		    if (cur_scsis->types[i] == MISCSI_CDROM
			&& not_part(cur_scsis->devs[i], tested_sd, freesd)) {
			if (probe_scsi_device(cur_scsis, i, cur_pathspec))  return 1;
			strcpy(freesd, cur_scsis->devs[i] + 2);
			freesd += strlen(cur_scsis->devs[i] + 2) + 1;
		    }
	} else if (!strcmp(cur_probe_text, MBPROBE_SCSI_HARDDISK)) {
	    for (cur_scsis = &scsis; cur_scsis != NULL; cur_scsis = cur_scsis->next)
		for (i = 0; i < cur_scsis->num; ++i)
		    if (cur_scsis->types[i] == MISCSI_HARDDISK
			&& not_part(cur_scsis->devs[i], tested_sd, freesd)) {
			if (probe_scsi_device(cur_scsis, i, cur_pathspec))  return 1;
			strcpy(freesd, cur_scsis->devs[i] + 2);
			freesd += strlen(cur_scsis->devs[i] + 2) + 1;
		    }
	} else if (!strcmp(cur_probe_text, MBPROBE_ALL)) {
	    if (!ide_cdrom_touched || !scsi_cdrom_touched) {
		free_partitions(&partitions);
		if (!ide_cdrom_touched)   touch_all_ide_cdroms();
		if (!scsi_cdrom_touched)  touch_all_scsi_cdroms();
		parse_partitions(&partitions);
	    }
	    for (cur_ides = &ides; cur_ides != NULL; cur_ides = cur_ides->next)
		for (i = 0; i < cur_ides->num; ++i)
		    if (not_part(cur_ides->devs[i], tested_hd, freehd)) {
			if (probe_ide_device(cur_ides, i, cur_pathspec))  return 1;
			strcpy(freehd, cur_ides->devs[i] + 2);
			freehd += strlen(cur_ides->devs[i] + 2) + 1;
		    }
	    for (cur_scsis = &scsis; cur_scsis != NULL; cur_scsis = cur_scsis->next)
		for (i = 0; i < cur_scsis->num; ++i)
		    if (not_part(cur_scsis->devs[i], tested_sd, freesd)) {
			if (probe_scsi_device(cur_scsis, i, cur_pathspec))  return 1;
			strcpy(freesd, cur_scsis->devs[i] + 2);
			freesd += strlen(cur_scsis->devs[i] + 2) + 1;
		    }
	} else
	    if (probe_device(cur_probe_text, cur_pathspec))  return 1;
    }
    return 0;
}


static int  not_part(const char *dev, const char *devices, const char *terminate) {
    const char *dev2 = dev + 2;
    while (devices < terminate) {
	if (!strcmp(devices, dev2))  return 0;
	devices += strlen(devices) + 1;
    }
    return 1;
}
static int  probe_ide_device(const ide_array *ides, int i, const char *pathspec) {
    int   j, l;
    const partition_array *curparts;
    if (ides->diskmask & (1 << i)) { /* This is a harddisk. */
	for (curparts = &partitions; curparts != NULL; curparts = curparts->next)
	    for (j = 0; j < curparts->num; ++j) {
		l = strlen(ides->devs[i]);
		if (!strncmp(ides->devs[i], curparts->names[j], l)
		    && l == strcspn(curparts->names[j], "0123456789")
		    && curparts->names[j][l] != 0)
		    if (probe_filesystem(curparts, j, pathspec))  return 1;
	    }
    } else { /* This is a cdrom. */
	for (curparts = &partitions; curparts != NULL; curparts = curparts->next)
	    for (j = 0; j < curparts->num; ++j)
		if (!strcmp(ides->devs[i], curparts->names[j]))
		    if (probe_filesystem(curparts, j, pathspec))  return 1;
    }
    return 0;
}

static int  probe_scsi_device(const scsi_array *scsis, int i, const char *pathspec) {
    int   j, l;
    const partition_array *curparts;
    if (scsis->types[i] == MISCSI_USBSTORAGE || scsis->types[i] == MISCSI_HARDDISK) {
	for (curparts = &partitions; curparts != NULL; curparts = curparts->next)
	    for (j = 0; j < curparts->num; ++j) {
		l = strlen(scsis->devs[i]);
		if (!strncmp(scsis->devs[i], curparts->names[j], l)
		    && l == strcspn(curparts->names[j], "0123456789")
		    && curparts->names[j][l] != 0)
		    if (probe_filesystem(curparts, j, pathspec))  return 1;
	    }
    } else if (scsis->types[i] == MISCSI_CDROM) {
	for (curparts = &partitions; curparts != NULL; curparts = curparts->next)
	    for (j = 0; j < curparts->num; ++j)
		if (!strcmp(scsis->devs[i], curparts->names[j]))
		    if (probe_filesystem(curparts, j, pathspec))  return 1;
    }
    return 0;
}

static int  mount_partition(const char *devfilename);
static int  probe_pathes(const char *dev, const char *pathspec);
static int  probe_filesystem(const partition_array *parts, int i, const char *pathspec) {
    char fnbuf[256];
    MBLOG((mdev, "Probe %s:", parts->names[i]));
    /* Create the device file and then mount, probe it. */
    snprintf(fnbuf, sizeof(fnbuf), MBPREFIX "/tmp/%s", parts->names[i]);
    mknod(fnbuf, 0660 | S_IFBLK, makedev(parts->major[i], parts->minor[i]));
    if (mount_partition(fnbuf)) {
	if (probe_pathes(parts->names[i], pathspec))  return 1;
	if (umount(mi_device_root))
	    MBLOG((mdev, " Umount failed."));
    }
    unlink(fnbuf);
    MBLOG((mdev, " not found.\n"));
    return 0;
}
static int  probe_device(const char *device, const char *pathspec) {
    int  rdev, minor;  char fnbuf[256];
    MBLOG((mdev, "Probe %s:", device));
    /* Check the validation of device. */
    if (device[0] != 'h' && device[0] != 's')  return 0;
    if (device[1] != 'd')  return 0;
    if (device[0] == 'h') {
	if (device[2] < 'a' || device[2] >= 'a' + 20)  return 0;
	minor = atoi(device + 3);
	if (minor <= 0 && minor >= 64)  return 0;
	rdev = ide_rdev[device[2] - 'a'] + minor;
    } else {
	if (device[2] < 'a' || device[2] >= 'a' + 26)  return 0;
	minor = atoi(device + 3);
	if (minor <= 0 && minor >= 16)  return 0;
	rdev = scsi_rdev[device[2] - 'a'] + minor;
    }
    /* Create the device file and then mount, probe it. */
    snprintf(fnbuf, sizeof(fnbuf), MBPREFIX "/tmp/%s", device);
    mknod(fnbuf, 0660 | S_IFBLK, rdev);
    if (mount_partition(fnbuf)) {
	if (probe_pathes(device, pathspec))  return 1;
	if (umount(mi_device_root))
	    MBLOG((mdev, " Unmount failed."));
    }
    unlink(fnbuf);
    MBLOG((mdev, " not found.\n"));
    return 0;
}

static int  mount_partition(const char *devfilename) {
    const char *fstype;
    if ((fstype = do_guess_fstype(devfilename)) == NULL)  return 0;
    MBLOG((mdev, " %s:", fstype));
    if (!strcmp(fstype, "swap"))  return 0;
    if (mount(devfilename, mi_device_root, fstype, MS_RDONLY, NULL)) {
	MBLOG((mdev, " Mount failed(%s).", strerror(errno)));
	return 0;
    }
    if (respos_device)  free(respos_device);
    respos_device = strdup(devfilename);
    if (respos_fstype)  free(respos_fstype);
    respos_fstype = strdup(fstype);
    return 1;
}

static int  probe_position(const char *path, off_t mi_initrd_size);
static int  probe_pathes(const char *dev, const char *pathspec) {

    const mxml_node_t  *srhpath, *curpath;  const char *curpathtext;
    off_t  mi_initrd_size;

    mi_initrd_size = EXT2FSSIZE * 1024 * 1024;
    if (pathspec) {
	srhpath = mxmlFindElement(xmlroot, xmlroot, "pathspec",
				  "name", pathspec, MXML_NO_DESCEND);
	if (srhpath == NULL) { /* pathspec is a real path. */
	    if (*pathspec == '/') ++pathspec;
	    MBLOG((mdev, " (/%s)", pathspec));
	    return  probe_position(pathspec, mi_initrd_size);
	}
    } else {
	srhpath = default_pathspec;
	if (srhpath == NULL) {
	    MBLOG((mdev, " Not any pathspec available for %s.", dev));
	    return 0;
	}
    }
    for (curpath = srhpath->child; curpath != NULL; curpath = curpath->next) {
	if (curpath->type != MXML_ELEMENT)  continue;
	if (strcmp(curpath->value.element.name, "path"))  continue;
	if (curpath->child->type != MXML_TEXT)  continue;
	curpathtext = curpath->child->value.text.string;
	if (*curpathtext == '/') ++curpathtext;
	MBLOG((mdev, " (/%s)", curpathtext));
	if (probe_position(curpathtext, mi_initrd_size))
	    return 1;
    }
    return 0;
}
static int  probe_position(const char *path, off_t mi_initrd_size) {
    struct stat st;
    snprintf(mi_initrd_fullpath, sizeof(mi_initrd_fullpath),
	     "%s/%s/%s", mi_device_root, path, mi_initrd_file);
    /* Got it when the file exists and it's size is correct. */
    if (!stat(mi_initrd_fullpath, &st) && st.st_size == mi_initrd_size) {
	mi_isofile_fullpath[0] = 0;
	if (respos_path)  free(respos_path);
	respos_path = strdup(path);
	respos_in_iso = 0;
	return 1;
    }
    snprintf(mi_isofile_fullpath, sizeof(mi_initrd_fullpath),
	     "%s/%s/" BOOTISO_FN, mi_device_root, path);
    if (stat(mi_isofile_fullpath, &st) || !S_ISREG(st.st_mode))
	return 0;
    if (lomount(LOOP_DEVFILE, mi_isofile_fullpath, mi_isoloop_root, "iso9660"))
	return 0;
    snprintf(mi_initrd_fullpath, sizeof(mi_initrd_fullpath),
	     "%s/" MIIMAGES_CDDIR "/%s", mi_isoloop_root, mi_initrd_file);
    if (!stat(mi_initrd_fullpath, &st) && st.st_size == mi_initrd_size) {
	if (respos_path)  free(respos_path);
	respos_path = strdup(path);
	respos_in_iso = 1;
	return 1;
    }
    loumount(LOOP_DEVFILE, mi_isoloop_root);
    return 0;
}

static int check_loopdev_unused(const char *device) {
    int              fd;
    struct stat      statbuf;
    struct loop_info loopinfo;
    if (stat(device, &statbuf)) {
	MBLOG((mdev, "stat %s failed.\n", device));
	return 1;
    }
    if (!S_ISBLK(statbuf.st_mode)) {
	MBLOG((mdev, "%s is not a block device file.\n", device));
	return 1;
    }
    fd = open(device, O_RDONLY);
    if (fd < 0) {
	MBLOG((mdev, "open %s failed.\n", device));
	return 1;
    }
    if (ioctl(fd, LOOP_GET_STATUS, &loopinfo) == 0) {
	MBLOG((mdev, "%s is used already.\n", device));
	return 1;
    }
    if (errno == ENXIO) {
	close(fd);
	return 0;
    }
    MBLOG((mdev, "ioctl LOOP_GET_STATUS on %s failed.\n", device));
    close(fd);
    return 1;
}
static int set_loop(const char *device, const char *file) {
    struct loop_info loopinfo;
    int              fd, ffd;

    if ((ffd = open(file, O_RDONLY)) < 0) {
	MBLOG((mdev, "open('%s', O_RDONLY) failed.\n", file));
	return 1;
    }
    if ((fd = open(device, O_RDONLY)) < 0) {
	MBLOG((mdev, "open('%s', O_RDONLY) failed.\n", device));
	close(ffd);
	return 1;
    }
    memset(&loopinfo, 0, sizeof(loopinfo));
    strncpy(loopinfo.lo_name, file, LO_NAME_SIZE);
    loopinfo.lo_name[LO_NAME_SIZE - 1] = 0;
    loopinfo.lo_offset = 0;
    loopinfo.lo_encrypt_key_size = 0;
    if (ioctl(fd, LOOP_SET_FD, ffd) < 0) {
	MBLOG((mdev, "ioctl('%s', LOOP_SET_FD, '%s') failed.\n", device, file));
	close(fd);
	close(ffd);
	return 1;
    }
    if (ioctl(fd, LOOP_SET_STATUS, &loopinfo) < 0) {
	(void)ioctl(fd, LOOP_CLR_FD, 0);
	MBLOG((mdev, "ioctl('%s', LOOP_SET_STATUS, &loopinfo) failed.\n", device));
	close(fd);
	close(ffd);
	return 1;
    }
    close(fd);
    close(ffd);
    return 0;
}
static int del_loop(const char *device) {
    int fd;
    if ((fd = open(device, O_RDONLY)) < 0) {
	MBLOG((mdev, "open('%s', O_RDONLY) failed.\n", device));
	return 1;
    }
    if (ioctl(fd, LOOP_CLR_FD, 0) < 0) {
	MBLOG((mdev, "ioctl('%s', LOOP_CLR_FD, 0) failed.\n", device));
	close(fd);
	return 1;
    }
    close(fd);
    return 0;
}

int lomount(const char *device, const char *file, const char *mntdir, const char *fstype) {
    if (check_loopdev_unused(device))  return -1;
    if (set_loop(device, file))  return -1;
    if (mount(device, mntdir, fstype, MS_RDONLY, NULL) == 0)  return 0;
    MBLOG((mdev, "mount('%s', '%s', '%s', MS_RDONLY, NULL) failed.\n",
	  device, mntdir, fstype));
    del_loop(device);
    return -1;
}
int loumount(const char *device, const char *mntdir) {
    int  r = 0;
    if (umount(mntdir) != 0) {
	MBLOG((mdev, "umount('%s') failed.\n", mntdir));
	r = 1;
    }
    if (del_loop(device)) {
	MBLOG((mdev, "del_loop('%s') failed.\n", device));
	r = 1;
    }
    return r;
}

static void mblog_start(void) {
    mblogfd = open("/magicbooter.log", O_CREAT | O_APPEND | O_WRONLY | O_SYNC, 0600);
    if (mblogfd < 0) {
	mfunc(mdev, "Can't open '/magicbooter.log': %d, %s\n", errno, strerror(errno));
    }
}
static void mblog_msg(int mdev, const char *fmt, ...) {
    char  buf[512];
    va_list  ap;

    if (mblogfd < 0)  return;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    write(mblogfd, buf, strlen(buf));
    va_end(ap);
}
static void mblog_end(void) {
    if (mblogfd < 0)  return;
    close(mblogfd);
    mblogfd = -1;
}
